#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "include/base-api/cf_base_api.h"
#include "include/base-api/cf_base_types.h"
#include "include/internal/cf_types.h"

#define CF_INTERFACE_CEHCK(ret, i, mod, func) \
    if (NULL == i.func) \
    { \
        ret = EINVAL; \
        cacheflex_t *cache = NULL; \
        CF_WRITE_LOG(CF_ERROR, "%s::%s null pointer", #mod, #func); \
    }

#define CF_MODULE_CONSTRUCT(cache, mod, args...) \
    ({ \
        int ret = 0; \
        if (NULL == (cache->mod.instance = cache->mod.interface.construct(args))) \
        { \
            ret = ENOMEM; \
            CF_WRITE_LOG(CF_ERROR, "construct instance of %s fail", #mod); \
        } \
        ret; \
    })

static int cf_index_interface_check(cf_index_interface_t i)
{
    int ret = 0;
    CF_INTERFACE_CEHCK(ret, i, index, construct);
    CF_INTERFACE_CEHCK(ret, i, index, destruct);
    CF_INTERFACE_CEHCK(ret, i, index, put);
    CF_INTERFACE_CEHCK(ret, i, index, get);
    CF_INTERFACE_CEHCK(ret, i, index, erase);
    CF_INTERFACE_CEHCK(ret, i, index, scan);
    return ret;
}

static int cf_store_interface_check(cf_store_interface_t i)
{
    int ret = 0;
    CF_INTERFACE_CEHCK(ret, i, store, construct);
    CF_INTERFACE_CEHCK(ret, i, store, destruct);
    CF_INTERFACE_CEHCK(ret, i, store, alloc);
    CF_INTERFACE_CEHCK(ret, i, store, free);
    CF_INTERFACE_CEHCK(ret, i, store, get_buffer);
    CF_INTERFACE_CEHCK(ret, i, store, get_level_num);
    CF_INTERFACE_CEHCK(ret, i, store, get_level_percent);
    CF_INTERFACE_CEHCK(ret, i, store, get_store_level);
    CF_INTERFACE_CEHCK(ret, i, store, promote_level);
    CF_INTERFACE_CEHCK(ret, i, store, demote_level);
    return ret;
}

static int cf_reclaim_interface_check(cf_reclaim_interface_t i)
{
    int ret = 0;
    CF_INTERFACE_CEHCK(ret, i, reclaim, construct);
    CF_INTERFACE_CEHCK(ret, i, reclaim, destruct);
    CF_INTERFACE_CEHCK(ret, i, reclaim, assign);
    CF_INTERFACE_CEHCK(ret, i, reclaim, access);
    CF_INTERFACE_CEHCK(ret, i, reclaim, reclaim);
    return ret;
}

static int cf_kv_ops_check(cf_kv_ops_t i)
{
    int ret = 0;
    CF_INTERFACE_CEHCK(ret, i, kv_ops, sizeof_key);
    CF_INTERFACE_CEHCK(ret, i, kv_ops, copy_key);
    CF_INTERFACE_CEHCK(ret, i, kv_ops, destroy_key);
    CF_INTERFACE_CEHCK(ret, i, kv_ops, sizeof_value);
    CF_INTERFACE_CEHCK(ret, i, kv_ops, copy_value);
    CF_INTERFACE_CEHCK(ret, i, kv_ops, destroy_value);
    CF_INTERFACE_CEHCK(ret, i, kv_ops, hash_key);
    CF_INTERFACE_CEHCK(ret, i, kv_ops, compare_key);
    return ret;
}

cacheflex_t *cf_init(const int64_t cache_size_limit,
                    const int64_t item_num_limit,
                    const cf_kv_store_type_t kv_store_type,
                    cf_kv_ops_t kv_ops,
                    cf_index_interface_t index_interface,
                    cf_store_interface_t store_interface,
                    cf_reclaim_interface_t reclaim_interface,
                    cf_generic_alloc_pt generic_alloc,
                    cf_generic_free_pt generic_free,
                    cf_write_log_pt write_log)
{
    cacheflex_t *cache = NULL;
    if (0 >= cache_size_limit
        || 0 >= item_num_limit
        || (CF_KV_STORE_SINGLE != kv_store_type
            && CF_KV_STORE_MULTI != kv_store_type)
        || 0 != cf_kv_ops_check(kv_ops)
        || 0 != cf_index_interface_check(index_interface)
        || 0 != cf_store_interface_check(store_interface)
        || 0 != cf_reclaim_interface_check(reclaim_interface)
        || NULL == generic_alloc
        || NULL == generic_free
        || NULL == write_log)
    {
        CF_WRITE_LOG(CF_ERROR, "invalid param cache_size_limit=%lld item_num_limit=%lld "
                    "generic_alloc=%p generic_free=%p write_log=%p",
                    cache_size_limit, item_num_limit, 
                    generic_alloc, generic_free, write_log);
    }
    else if (NULL == (cache = (cacheflex_t*)generic_alloc(sizeof(cacheflex_t))))
    {
        CF_WRITE_LOG(CF_ERROR, "alloc cacheflex instance fail size=%u", sizeof(cacheflex_t));
    }
    else
    {
        memset(cache, 0, sizeof(cacheflex_t));
        int64_t reclaim_num = 0;
        if (CF_KV_STORE_SINGLE == kv_store_type)
        {
            reclaim_num = item_num_limit;
        }
        else
        {
            reclaim_num = cache_size_limit / CF_MULTI_STORE_SIZE;
        }
        cache->cache_size_limit = cache_size_limit;
        cache->item_num_limit = item_num_limit;
        cache->alloc = generic_alloc;
        cache->free = generic_free;
        cache->write_log = write_log;
        cache->index.interface = index_interface;
        cache->store.interface = store_interface;
        cache->reclaim.interface = reclaim_interface;
        if (0 != CF_MODULE_CONSTRUCT(cache, index, item_num_limit)
            || 0 != CF_MODULE_CONSTRUCT(cache, store, cache_size_limit)
            || 0 != CF_MODULE_CONSTRUCT(cache, reclaim, reclaim_num))
        {
            cf_destroy(cache);
            cache = NULL;
        }
        else
        {
            CF_WRITE_LOG(CF_INFO, "cacheflex=%p init succ cache_size_limit=%lld item_num_limit=%lld "
                        "index=%p store=%p reclaim=%p",
                        cache, cache_size_limit, item_num_limit,
                        cache->index.instance, cache->store.instance, cache->reclaim.instance);
        }
    }
    return cache;
}

void cf_destroy(cacheflex_t *cache)
{
    if (NULL != cache)
    {
        if (NULL != cache->index.instance
            && 0 == cf_index_interface_check(cache->index.interface))
        {
            cache->index.interface.destruct(cache->index.instance);
            cache->index.instance = NULL;
        }
        if (NULL != cache->store.instance
            && 0 == cf_store_interface_check(cache->store.interface))
        {
            cache->store.interface.destruct(cache->store.instance);
            cache->store.instance = NULL;
        }
        if (NULL != cache->reclaim.instance
            && 0 == cf_reclaim_interface_check(cache->reclaim.interface))
        {
            cache->reclaim.interface.destruct(cache->reclaim.instance);
            cache->reclaim.instance = NULL;
        }
        if (NULL != cache->free)
        {
            cache->free(cache);
        }
    }
}

